/*	$OpenBSD: select.c,v 1.2 2002/06/25 15:50:15 mickey Exp $	*/

/*
 * Copyright 2000-2002 Niels Provos <provos@citi.umich.edu>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#ifndef HAVE_CONFIG_H
#include <config.h>
#endif

#ifdef WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winsock2.h>
#undef WIN32_LEAN_AND_MEAN
#endif
#include <sys/types.h>
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#include <sys/queue.h>
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <errno.h>
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#include <assert.h>

#include <event.h>
#include <event-internal.h>
#include <evsignal.h>
#include <evutil.h>
#include <log.h>

struct event_base *evsignal_base = NULL;

static void evsignal_handler (int sig);

/* Callback for when the signal handler write a byte to our signaling socket */
static void
evsignal_cb (int fd, short what, void *arg)
{
  static char signals[100];

#ifdef WIN32
  SSIZE_T n;
#else
  ssize_t n;
#endif

  (void) what;
  (void) arg;

  n = recv (fd, signals, sizeof (signals), 0);

  if (n == -1)
    event_err (1, "%s: read", __func__);
}

#ifdef HAVE_SETFD
#define FD_CLOSEONEXEC(x) do { \
      if (fcntl(x, F_SETFD, 1) == -1) \
        event_warn("fcntl(%d, F_SETFD)", x); \
    } while (0)
#else
#define FD_CLOSEONEXEC(x)
#endif

void
evsignal_init (struct event_base *base)
{
  /*
   * Our signal handler is going to write to one end of the socket
   * pair to wake up our event loop.  The event loop then scans for
   * signals that got delivered.
   */
  if (evutil_socketpair (AF_UNIX, SOCK_STREAM, 0, base->sig.ev_signal_pair) == -1)
    event_err (1, "%s: socketpair", __func__);

  FD_CLOSEONEXEC (base->sig.ev_signal_pair[0]);

  FD_CLOSEONEXEC (base->sig.ev_signal_pair[1]);

  base->sig.sh_old = NULL;

  base->sig.sh_old_max = 0;

  base->sig.evsignal_caught = 0;

  memset (&base->sig.evsigcaught, 0, sizeof (sig_atomic_t) *NSIG);

  evutil_make_socket_nonblocking (base->sig.ev_signal_pair[0]);

  event_set (&base->sig.ev_signal, base->sig.ev_signal_pair[1],
             EV_READ | EV_PERSIST, evsignal_cb, &base->sig.ev_signal);

  base->sig.ev_signal.ev_base = base;

  base->sig.ev_signal.ev_flags |= EVLIST_INTERNAL;
}

/* Helper: set the signal handler for evsignal to handler in base, so that
 * we can restore the original handler when we clear the current one. */
int
_evsignal_set_handler (struct event_base *base,
                       int evsignal, void (*handler) (int) )
{
#ifdef HAVE_SIGACTION

  struct sigaction sa;
#else
  ev_sighandler_t sh;
#endif

  struct evsignal_info *sig = &base->sig;
  void *p;

  /*
   * resize saved signal handler array up to the highest signal number.
   * a dynamic array is used to keep footprint on the low side.
   */

  if (evsignal >= sig->sh_old_max)
    {
      event_debug ( ("%s: evsignal (%d) >= sh_old_max (%d), resizing",
                     __func__, evsignal, sig->sh_old_max) );
      sig->sh_old_max = evsignal + 1;
      p = realloc (sig->sh_old, sig->sh_old_max * sizeof * sig->sh_old);

      if (p == NULL)
        {
          event_warn ("realloc");
          return (-1);
        }

      sig->sh_old = p;
    }

  /* allocate space for previous handler out of dynamic array */
  sig->sh_old[evsignal] = malloc (sizeof * sig->sh_old[evsignal]);

  if (sig->sh_old[evsignal] == NULL)
    {
      event_warn ("malloc");
      return (-1);
    }

  /* save previous handler and setup new handler */
#ifdef HAVE_SIGACTION
  memset (&sa, 0, sizeof (sa) );

  sa.sa_handler = handler;

  sa.sa_flags |= SA_RESTART;

  sigfillset (&sa.sa_mask);

  if (sigaction (evsignal, &sa, sig->sh_old[evsignal]) == -1)
    {
      event_warn ("sigaction");
      free (sig->sh_old[evsignal]);
      return (-1);
    }

#else
  if ( (sh = signal (evsignal, handler) ) == SIG_ERR)
    {
      event_warn ("signal");
      free (sig->sh_old[evsignal]);
      return (-1);
    }

  *sig->sh_old[evsignal] = sh;

#endif

  return (0);
}

int
evsignal_add (struct event *ev)
{
  int evsignal;

  struct event_base *base = ev->ev_base;

  struct evsignal_info *sig = &ev->ev_base->sig;

  if (ev->ev_events & (EV_READ | EV_WRITE) )
    event_errx (1, "%s: EV_SIGNAL incompatible use", __func__);

  evsignal = EVENT_SIGNAL (ev);

  event_debug ( ("%s: %p: changing signal handler", __func__, ev) );

  if (_evsignal_set_handler (base, evsignal, evsignal_handler) == -1)
    return (-1);

  /* catch signals if they happen quickly */
  evsignal_base = base;

  if (!sig->ev_signal_added)
    {
      sig->ev_signal_added = 1;
      event_add (&sig->ev_signal, NULL);
    }

  return (0);
}

int
_evsignal_restore_handler (struct event_base *base, int evsignal)
{
  int ret = 0;

  struct evsignal_info *sig = &base->sig;
#ifdef HAVE_SIGACTION

  struct sigaction *sh;
#else
  ev_sighandler_t *sh;
#endif

  /* restore previous handler */
  sh = sig->sh_old[evsignal];
  sig->sh_old[evsignal] = NULL;
#ifdef HAVE_SIGACTION

  if (sigaction (evsignal, sh, NULL) == -1)
    {
      event_warn ("sigaction");
      ret = -1;
    }

#else
  if (signal (evsignal, *sh) == SIG_ERR)
    {
      event_warn ("signal");
      ret = -1;
    }

#endif
  free (sh);

  return ret;
}

int
evsignal_del (struct event *ev)
{
  event_debug ( ("%s: %p: restoring signal handler", __func__, ev) );
  return _evsignal_restore_handler (ev->ev_base, EVENT_SIGNAL (ev) );
}

static void
evsignal_handler (int sig)
{
  int save_errno = errno;

  if (evsignal_base == NULL)
    {
      event_warn (
        "%s: received signal %d, but have no base configured",
        __func__, sig);
      return;
    }

  evsignal_base->sig.evsigcaught[sig]++;

  evsignal_base->sig.evsignal_caught = 1;

#ifndef HAVE_SIGACTION
  signal (sig, evsignal_handler);
#endif

  /* Wake up our notification mechanism */
  send (evsignal_base->sig.ev_signal_pair[0], "a", 1, 0);
  errno = save_errno;
}

void
evsignal_process (struct event_base *base)
{

  struct event *ev;
  sig_atomic_t ncalls;

  base->sig.evsignal_caught = 0;
  TAILQ_FOREACH (ev, &base->sig.signalqueue, ev_signal_next)
  {
    ncalls = base->sig.evsigcaught[EVENT_SIGNAL (ev) ];

    if (ncalls)
      {
        if (! (ev->ev_events & EV_PERSIST) )
          event_del (ev);

        event_active (ev, EV_SIGNAL, ncalls);

        base->sig.evsigcaught[EVENT_SIGNAL (ev) ] = 0;
      }
  }
}

void
evsignal_dealloc (struct event_base *base)
{
  if (base->sig.ev_signal_added)
    {
      event_del (&base->sig.ev_signal);
      base->sig.ev_signal_added = 0;
    }

  assert (TAILQ_EMPTY (&base->sig.signalqueue) );

  EVUTIL_CLOSESOCKET (base->sig.ev_signal_pair[0]);
  base->sig.ev_signal_pair[0] = -1;
  EVUTIL_CLOSESOCKET (base->sig.ev_signal_pair[1]);
  base->sig.ev_signal_pair[1] = -1;
  base->sig.sh_old_max = 0;

  /* per index frees are handled in evsignal_del() */
  free (base->sig.sh_old);
}
